package controllers

import (
	"fmt"
	sysv1 "ingress-operator/api/v1"
	"io/ioutil"
	"os"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
)

// 根据Unit.Spec生成其所有的own resource
func (r *IngressNginxReconciler) getOwnResources(instance *sysv1.IngressNginx) ([]OwnResource, error) {
	var ownResources []OwnResource

	leaderConfigName := "ingress-controller-leader-" + instance.Spec.Zone + "-" + instance.Namespace + "-" + instance.Spec.Zone
	controllerDeploymentName := "nginx-ingress-" + instance.Spec.Zone + "-controller"
	defaultDeployName := "default-http-backend-" + instance.Spec.Zone
	defaultServiceName := "default-http-backend-" + instance.Spec.Zone
	saName := "nginx-ingress-serviceaccount-" + instance.Spec.Zone
	roleName := "nginx-ingress-role-" + instance.Spec.Zone
	roleBindingName := "nginx-ingress-role-nisa-binding-" + instance.Spec.Zone
	clusterRoleName := "nginx-ingress-clusterrole-" + instance.Namespace + "-" + instance.Spec.Zone
	clsuterRoleBindingName := "nginx-ingress-clusterrole-nisa-binding-" + instance.Namespace + "-" + instance.Spec.Zone
	tcpConfigName := "tcp-services-" + instance.Spec.Zone
	udpConfigName := "udp-services-" + instance.Spec.Zone
	nginxConfigName := "nginx-configuration-" + instance.Spec.Zone
	tmplConfigName := "nginx-template-" + instance.Spec.Zone
	var pds int32 = 600
	var rcs int32 = 2
	var rcs1 int32 = 1
	var rhl int32 = 10
	var tgps int64 = 30
	var userID int64 = 500
	var httpPort int = instance.Spec.HttpPort
	var httpsPort int = httpPort + 100
	var statusPort int = httpsPort + 100
	var healthzPort int = statusPort + 100
	var defaultPort int = healthzPort + 100
	var streamPort int = defaultPort + 100
	var profilerPort int = streamPort + 100
	hpDirectoryOrCreate := corev1.HostPathDirectoryOrCreate
	trueBool := true

	// controllerDeployment
	ownDeployment := sysv1.OwnDeployment{
		Name: controllerDeploymentName,
		Type: sysv1.ControllerDeployment,
		Spec: appsv1.DeploymentSpec{
			ProgressDeadlineSeconds: &pds,
			Replicas:                &rcs,
			RevisionHistoryLimit:    &rhl,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app":                                "nginx-ingress",
					"ingress-controller.99bill.com/zone": instance.Spec.Zone,
				},
			},
			Strategy: appsv1.DeploymentStrategy{
				Type: appsv1.RollingUpdateDeploymentStrategyType,
				RollingUpdate: &appsv1.RollingUpdateDeployment{
					MaxUnavailable: &intstr.IntOrString{Type: intstr.String, StrVal: "50%"},
					MaxSurge:       &intstr.IntOrString{Type: intstr.String, StrVal: "50%"},
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app":                                "nginx-ingress",
						"ingress-controller.99bill.com/zone": instance.Spec.Zone,
					},
				},
				Spec: corev1.PodSpec{
					DNSPolicy:                     corev1.DNSPolicy("ClusterFirst"),
					HostNetwork:                   true,
					RestartPolicy:                 corev1.RestartPolicyAlways,
					ServiceAccountName:            "nginx-ingress-serviceaccount-" + instance.Spec.Zone,
					TerminationGracePeriodSeconds: &tgps,
					Affinity: &corev1.Affinity{
						NodeAffinity: &corev1.NodeAffinity{
							RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
								NodeSelectorTerms: []corev1.NodeSelectorTerm{
									{
										MatchExpressions: []corev1.NodeSelectorRequirement{
											{
												Key:      "nginx-ingress_" + instance.Namespace + "_" + instance.Spec.Zone,
												Operator: corev1.NodeSelectorOpIn,
												Values:   []string{"true"},
											},
										},
									},
								},
							},
						},
					},
					Volumes: []corev1.Volume{
						{
							Name: "init-dir",
							VolumeSource: corev1.VolumeSource{
								HostPath: &corev1.HostPathVolumeSource{
									Path: "/opt",
									Type: &hpDirectoryOrCreate,
								},
							},
						},
						{
							Name: "log",
							VolumeSource: corev1.VolumeSource{
								HostPath: &corev1.HostPathVolumeSource{
									Path: "/opt/log/nginx/" + instance.Namespace,
									Type: &hpDirectoryOrCreate,
								},
							},
						},
						{
							Name: "nginx-template-volume",
							VolumeSource: corev1.VolumeSource{
								ConfigMap: &corev1.ConfigMapVolumeSource{
									LocalObjectReference: corev1.LocalObjectReference{
										Name: "nginx-template-" + instance.Spec.Zone,
									},
									Items: []corev1.KeyToPath{
										{
											Key:  "nginx.tmpl",
											Path: "nginx.tmpl",
										},
									},
								},
							},
						},
					},
					InitContainers: []corev1.Container{
						{
							Name:  "initlog",
							Image: "vipharbor.99bill.com/base/busybox",
							Env: []corev1.EnvVar{
								{
									Name: "POD_NAMESPACE",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.namespace",
										},
									},
								},
							},
							Command:         []string{"chown", "-R", "500.500", "/opt/log/nginx/$(POD_NAMESPACE)"},
							ImagePullPolicy: corev1.PullIfNotPresent,
							SecurityContext: &corev1.SecurityContext{Privileged: &trueBool},
							VolumeMounts: []corev1.VolumeMount{
								{
									Name:      "init-dir",
									MountPath: "/opt",
									ReadOnly:  false,
								},
							},
						},
						{
							Name:            "sysctl",
							Image:           "vipharbor.99bill.com/base/busybox",
							Command:         []string{"sh", "-c", "sysctl -w fs.file-max=999999;sysctl -w net.ipv4.tcp_tw_reuse=1;sysctl -w net.ipv4.tcp_keepalive_time=600;sysctl -w net.ipv4.tcp_fin_timeout=30;sysctl -w net.ipv4.tcp_max_tw_buckets=5000;sysctl -w net.ipv4.ip_local_port_range=1024 61000;sysctl -w net.ipv4.tcp_rmem=4096 32768 262142;sysctl -w net.ipv4.tcp_wmem=4096 32768 262142;sysctl -w net.core.netdev_max_backlog=8096;sysctl -w net.core.rmem_default=262144;sysctl -w net.core.wmem_default=262144;sysctl -w net.core.rmem_max=2097152;sysctl -w net.core.wmem_max=2097152;sysctl -w net.ipv4.tcp_syncookies=1;sysctl -w net.ipv4.tcp_max_syn_backlog=1024;sysctl -w net.ipv4.tcp_tw_recycle=1;sysctl -w net.core.somaxconn=1024"},
							SecurityContext: &corev1.SecurityContext{Privileged: &trueBool},
						},
					},
					Containers: []corev1.Container{
						{
							Name:            "nginx-ingress-controller",
							Image:           "vipharbor.99bill.com/kubernetes-ingress-controller/nginx-ingress-controller-99bill:v1.1.1.1",
							ImagePullPolicy: corev1.PullIfNotPresent,
							Args:            []string{"/nginx-ingress-controller", "--election-id=ingress-controller-leader-" + instance.Spec.Zone, "--watch-namespace=$(POD_NAMESPACE)", "--configmap=$(POD_NAMESPACE)/nginx-configuration-" + instance.Spec.Zone, "--annotations-prefix=nginx.ingress.kubernetes.io", "--tcp-services-configmap=$(POD_NAMESPACE)/tcp-services-" + instance.Spec.Zone, "--udp-services-configmap=$(POD_NAMESPACE)/udp-services-" + instance.Spec.Zone, "--ingress-class=$(POD_NAMESPACE)-" + instance.Spec.Zone, "--http-port=" + fmt.Sprint(httpPort), "--https-port=" + fmt.Sprint(httpsPort), "--status-port=" + fmt.Sprint(statusPort), "--healthz-port=" + fmt.Sprint(healthzPort), "--default-server-port=" + fmt.Sprint(defaultPort), "--stream-port=" + fmt.Sprint(streamPort), "--profiler-port=" + fmt.Sprint(profilerPort), "--default-backend-service=$(POD_NAMESPACE)/default-http-backend-" + instance.Spec.Zone},
							Env: []corev1.EnvVar{
								{
									Name: "POD_NAME",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.name",
										},
									},
								},
								{
									Name: "POD_NAMESPACE",
									ValueFrom: &corev1.EnvVarSource{
										FieldRef: &corev1.ObjectFieldSelector{
											APIVersion: "v1",
											FieldPath:  "metadata.namespace",
										},
									},
								},
								{
									Name:  "LD_PRELOAD",
									Value: "/usr/local/lib/libmimalloc.so",
								},
							},
							Ports: []corev1.ContainerPort{
								{
									Name:          "http",
									HostPort:      int32(httpPort),
									ContainerPort: int32(httpPort),
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "https",
									HostPort:      int32(httpsPort),
									ContainerPort: int32(httpsPort),
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "status",
									HostPort:      int32(statusPort),
									ContainerPort: int32(statusPort),
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "healthz",
									HostPort:      int32(healthzPort),
									ContainerPort: int32(healthzPort),
									Protocol:      corev1.ProtocolTCP,
								},
								{
									Name:          "default",
									HostPort:      int32(defaultPort),
									ContainerPort: int32(defaultPort),
									Protocol:      corev1.ProtocolTCP,
								},
							},
							Lifecycle: &corev1.Lifecycle{
								PreStop: &corev1.Handler{
									Exec: &corev1.ExecAction{Command: []string{"/wait-shutdown"}},
								},
							},
							LivenessProbe: &corev1.Probe{
								InitialDelaySeconds: int32(5),
								TimeoutSeconds:      int32(1),
								PeriodSeconds:       int32(10),
								SuccessThreshold:    int32(1),
								FailureThreshold:    int32(5),
								Handler: corev1.Handler{
									HTTPGet: &corev1.HTTPGetAction{
										Path:   "/healthz",
										Port:   intstr.IntOrString{IntVal: int32(healthzPort)},
										Scheme: corev1.URISchemeHTTP,
									},
								},
							},
							ReadinessProbe: &corev1.Probe{
								TimeoutSeconds:   int32(1),
								PeriodSeconds:    int32(10),
								SuccessThreshold: int32(1),
								FailureThreshold: int32(3),
								Handler: corev1.Handler{
									HTTPGet: &corev1.HTTPGetAction{
										Path:   "/healthz",
										Port:   intstr.IntOrString{IntVal: int32(healthzPort)},
										Scheme: corev1.URISchemeHTTP,
									},
								},
							},
							SecurityContext: &corev1.SecurityContext{
								Capabilities: &corev1.Capabilities{
									Add:  []corev1.Capability{"NET_BIND_SERVICE"},
									Drop: []corev1.Capability{"ALL"},
								},
								RunAsUser: &userID,
							},
							Resources: corev1.ResourceRequirements{
								Limits: corev1.ResourceList{
									corev1.ResourceMemory: *resource.NewQuantity(17179869184, resource.BinarySI),
								},
								Requests: corev1.ResourceList{
									corev1.ResourceMemory: *resource.NewQuantity(104857600, resource.BinarySI),
								},
							},
							VolumeMounts: []corev1.VolumeMount{
								{
									Name:      "log",
									MountPath: "/var/log/nginx",
									ReadOnly:  false,
								},
								{
									Name:      "nginx-template-volume",
									MountPath: "/etc/nginx/template",
									ReadOnly:  true,
								},
							},
						},
					},
				},
			},
		},
	}
	// ownDeployment.Spec.Template.Labels = instance.Spec.Selector.MatchLabels
	ownResources = append(ownResources, &ownDeployment)

	// ownDefaultDeployment
	ownDefaultDeployment := sysv1.OwnDeployment{
		Name: defaultDeployName,
		Type: sysv1.DefaultDeployment,
		Spec: appsv1.DeploymentSpec{
			ProgressDeadlineSeconds: &pds,
			Replicas:                &rcs1,
			RevisionHistoryLimit:    &rhl,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app":                                "default-http-backend",
					"ingress-controller.99bill.com/zone": instance.Spec.Zone,
				},
			},
			Strategy: appsv1.DeploymentStrategy{
				RollingUpdate: &appsv1.RollingUpdateDeployment{
					MaxUnavailable: &intstr.IntOrString{IntVal: 1},
					MaxSurge:       &intstr.IntOrString{IntVal: 1},
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app":                                "default-http-backend",
						"ingress-controller.99bill.com/zone": instance.Spec.Zone,
					},
				},
				Spec: corev1.PodSpec{
					RestartPolicy:                 corev1.RestartPolicyAlways,
					TerminationGracePeriodSeconds: &tgps,
					DNSPolicy:                     corev1.DNSPolicy("ClusterFirst"),
					Affinity: &corev1.Affinity{
						NodeAffinity: &corev1.NodeAffinity{
							RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
								NodeSelectorTerms: []corev1.NodeSelectorTerm{
									{
										MatchExpressions: []corev1.NodeSelectorRequirement{
											{
												Key:      "nginx-ingress_" + instance.Namespace + "_" + instance.Spec.Zone,
												Operator: corev1.NodeSelectorOpIn,
												Values:   []string{"true"},
											},
										},
									},
								},
							},
						},
					},
					Containers: []corev1.Container{
						{
							Name:            "default-http-backend",
							Image:           "vipharbor.99bill.com/kubernetes-ingress-controller/custom-error-pages-amd64:0.4.99billv1",
							ImagePullPolicy: corev1.PullIfNotPresent,
							Ports: []corev1.ContainerPort{
								{
									Name:          "http",
									Protocol:      corev1.ProtocolTCP,
									ContainerPort: 8080,
								},
							},
							Resources: corev1.ResourceRequirements{
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:    resource.Quantity{Format: "100Mi"},
									corev1.ResourceMemory: resource.Quantity{Format: "200Mi"},
								},
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    resource.Quantity{Format: "10m"},
									corev1.ResourceMemory: resource.Quantity{Format: "20Mi"},
								},
							},
							LivenessProbe: &corev1.Probe{
								InitialDelaySeconds: 30,
								TimeoutSeconds:      5,
								PeriodSeconds:       10,
								SuccessThreshold:    1,
								FailureThreshold:    3,
								Handler: corev1.Handler{
									HTTPGet: &corev1.HTTPGetAction{
										Path:   "/healthz",
										Port:   intstr.IntOrString{IntVal: int32(8080)},
										Scheme: corev1.URISchemeHTTP,
									},
								},
							},
						},
					},
				},
			},
		},
	}
	ownResources = append(ownResources, &ownDefaultDeployment)

	// DefaultService
	ownDefaultService := sysv1.OwnService{
		Name: defaultServiceName,
		Ports: []corev1.ServicePort{
			{
				Protocol: corev1.ProtocolTCP,
				Port:     80,
				TargetPort: intstr.IntOrString{
					StrVal: "http",
					Type:   intstr.String,
				},
			},
		},
	}
	ownResources = append(ownResources, &ownDefaultService)

	// ServiceAccount
	ownSA := sysv1.OwnServiceAccount{
		Name: saName,
	}
	ownResources = append(ownResources, &ownSA)

	// Role
	ownRole := sysv1.OwnRole{
		Name: roleName,
		Rules: []rbacv1.PolicyRule{
			{
				APIGroups: []string{""},
				Resources: []string{"namespaces"},
				Verbs:     []string{"get"},
			},
			{
				APIGroups: []string{""},
				Resources: []string{"configmaps", "pods", "secrets", "endpoints"},
				Verbs:     []string{"get", "list", "watch"},
			},
			{
				APIGroups: []string{""},
				Resources: []string{"services"},
				Verbs:     []string{"get", "list", "watch", "update"},
			},
			{
				APIGroups: []string{"extensions", "networking.k8s.io"},
				Resources: []string{"ingresses"},
				Verbs:     []string{"get", "list", "watch"},
			},
			{
				APIGroups: []string{"extensions", "networking.k8s.io"},
				Resources: []string{"ingresses/status"},
				Verbs:     []string{"update"},
			},
			{
				APIGroups: []string{"networking.k8s.io"},
				Resources: []string{"ingressclasses"},
				Verbs:     []string{"get", "list", "watch"},
			},
			{
				APIGroups:     []string{""},
				Resources:     []string{"configmaps"},
				ResourceNames: []string{leaderConfigName},
				Verbs:         []string{"get", "update"},
			},
			{
				APIGroups: []string{""},
				Resources: []string{"configmaps"},
				Verbs:     []string{"create"},
			},
			{
				APIGroups: []string{""},
				Resources: []string{"endpoints"},
				Verbs:     []string{"create", "get", "update"},
			},
			{
				APIGroups: []string{"", "events.k8s.io"},
				Resources: []string{"events"},
				Verbs:     []string{"create", "patch"},
			},
		},
	}
	ownResources = append(ownResources, &ownRole)

	// RoleBinding
	ownRoleBinding := sysv1.OwnRoleBinding{
		Name: roleBindingName,
		RoleRef: rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "Role",
			Name:     roleName,
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      saName,
				Namespace: instance.Namespace,
			},
		},
	}
	ownResources = append(ownResources, &ownRoleBinding)

	// ClusterRole
	ownClusterRole := sysv1.OwnClusterRole{
		Name: clusterRoleName,
		Rules: []rbacv1.PolicyRule{
			{
				APIGroups: []string{""},
				Resources: []string{"configmaps", "endpoints", "nodes", "pods", "secrets"},
				Verbs:     []string{"list", "watch"},
			},
			{
				APIGroups: []string{""},
				Resources: []string{"nodes"},
				Verbs:     []string{"get"},
			},
			{
				APIGroups: []string{""},
				Resources: []string{"services"},
				Verbs:     []string{"get", "list", "watch", "update"},
			},
			{
				APIGroups: []string{"extensions", "networking.k8s.io"},
				Resources: []string{"ingresses"},
				Verbs:     []string{"get", "list", "watch"},
			},
			{
				APIGroups: []string{"", "events.k8s.io"},
				Resources: []string{"events"},
				Verbs:     []string{"create", "patch"},
			},
			{
				APIGroups: []string{"extensions", "networking.k8s.io"},
				Resources: []string{"ingresses/status"},
				Verbs:     []string{"update"},
			},
			{
				APIGroups: []string{"networking.k8s.io"},
				Resources: []string{"ingressclasses"},
				Verbs:     []string{"get", "list", "watch"},
			},
		},
	}
	ownResources = append(ownResources, &ownClusterRole)

	// clusterRoleBinding
	ownClusterRoleBinding := sysv1.OwnClusterRoleBinding{
		Name: clsuterRoleBindingName,
		RoleRef: rbacv1.RoleRef{
			APIGroup: "rbac.authorization.k8s.io",
			Kind:     "ClusterRole",
			Name:     clusterRoleName,
		},
		Subjects: []rbacv1.Subject{
			{
				Kind:      "ServiceAccount",
				Name:      saName,
				Namespace: instance.Namespace,
			},
		},
	}
	ownResources = append(ownResources, &ownClusterRoleBinding)

	// ConfigMap TcpService
	ownTcpService := sysv1.OwnConfigMap{
		Name: tcpConfigName,
		Data: instance.Spec.TcpData,
	}
	ownResources = append(ownResources, &ownTcpService)

	// ConfigMap UdpService
	ownUdpService := sysv1.OwnConfigMap{
		Name: udpConfigName,
		Data: instance.Spec.UdpData,
	}
	ownResources = append(ownResources, &ownUdpService)

	// ConfigMap LeaderConfig
	ownLeaderConfig := sysv1.OwnConfigMap{
		Name: leaderConfigName,
	}
	ownResources = append(ownResources, &ownLeaderConfig)

	// ConfigMap NginxConfig
	ownNginxConfig := sysv1.OwnConfigMap{
		Name: nginxConfigName,
		Data: GetNginxConfig(instance),
	}
	ownResources = append(ownResources, &ownNginxConfig)

	// ConfigMap TmplConfig
	var data []byte
	if f, err := os.OpenFile("nginx.tmpl", os.O_RDONLY, 0600); err != nil {
		r.Log.Error(err, "Open file nginx.tmpl failed.")
		return nil, err
	} else {
		data, err = ioutil.ReadAll(f)
	}
	ownTmplConfig := sysv1.OwnConfigMap{
		Name: tmplConfigName,
		Data: map[string]string{"nginx.tmpl": string(data)},
	}
	ownResources = append(ownResources, &ownTmplConfig)

	// 将关联的资源(svc/ing/pvc)加入ownResources中
	/*
		if instance.Spec.RelationResource.Service != nil {
			ownResources = append(ownResources, instance.Spec.RelationResource.Service)
		}
		if instance.Spec.RelationResource.ServiceAccount != nil {
			ownResources = append(ownResources, instance.Spec.RelationResource.ServiceAccount)
		}
		if instance.Spec.RelationResource.Role != nil {
			ownResources = append(ownResources, instance.Spec.RelationResource.Role)
		}
	*/
	return ownResources, nil
}

func GetNginxConfig(instance *sysv1.IngressNginx) map[string]string {
	data := map[string]string{
		"log-format-upstream":        "$http_x_forwarded_for $time_iso8601 $request $status $request_time $upstream_addr $upstream_status $upstream_response_time $http_referer $host $remote_addr",
		"log-format-stream":          "[$time_local] - $protocol - $status - $bytes_sent - $bytes_received - $session_time",
		"proxy-buffer-size":          "8k",
		"proxy-connect-timeout":      "30",
		"proxy-next-upstream":        "off",
		"proxy-set-headers":          instance.Namespace + "/proxy-custom-headers-" + instance.Spec.Zone,
		"add-headers":                instance.Namespace + "/add-headers-" + instance.Spec.Zone,
		"worker-processes":           "2",
		"server-tokens":              "false",
		"custom-http-errors":         "404,403,500,412",
		"worker-shutdown-timeout":    "0s",
		"use-gzip":                   "true",
		"gzip-level":                 "5",
		"gzip-min-length":            "10240",
		"gzip-types":                 "text/plain text/xml text/css application/x-javascript",
		"keep-alive":                 "10",
		"upstream-keepalive-timeout": "10",
	}

	for k, v := range instance.Spec.NginxConfig {
		dValue, ok := data[k]
		if ok {
			if dValue != v {
				data[k] = v
			}
		} else {
			data[k] = v
		}
	}

	return data
}
